Repository navigation
fix(openclaw): skip conversational opt-out directives and inline secrets in shouldCapture - #1355
harrymove-ctrl wants to merge 11 commits into
Conversation
ducnmm
left a comment
There was a problem hiding this comment.
The tightened opt-out patterns now miss ordinary explicit requests not to save a turn. Offline probes of the current capture module return hasCaptureOptOut=false and shouldCapture=true for the examples below; full package/live tests were not rerun.
| "i", | ||
| ), | ||
| new RegExp( | ||
| `${IMPERATIVE_NEGATION_PREFIX}(?:do\\s+not|don['\\u2019]?t|never)\\s+(?:ever\\s+)?(?:save|store|record|capture|keep)(?:\\s+(?:this|that|these|those|it|anything|what\\s+i|my\\b|our\\b|(?:in|to)\\s+memory|the\\s+(?:conversation|chat|message|turn|discussion|note|notes|details))\\b|(?=\\s*(?:$|[.!?)\\]])))`, |
There was a problem hiding this comment.
[P2] Keep ordinary do-not-save phrases in the opt-out filter
The new object whitelist after save/store excludes common explicit instructions such as “Please don’t save any of this: I prefer working from Tokyo next month” and “Do not save or remember this: my temporary medical appointment is at 3 PM.” Both now return hasCaptureOptOut=false and shouldCapture=true in this exact module, whereas the previous broader pattern rejected them. Because the hook uses the same predicate to suppress trailing assistant replies, the opted-out content and its echoes can reach analyze(). Extend the imperative grammar to cover these forms and add hook-level regression tests while retaining the ordinary developer-prose cases.
There was a problem hiding this comment.
Extended the opt-out grammar to support compound verbs like save or remember and any-of targets like don't save any of this. Added unit and hook-level regression tests in commit 6d0049e.
ducnmm
left a comment
There was a problem hiding this comment.
The two previously reported forms are now excluded correctly, but the combined pattern introduces another opt-out regression. Four focused opt-out tests passed using the current TypeScript source with Node type stripping; an offline hook probe still forwarded the explicit do-not-remember turn and its assistant acknowledgement to analyze(). Full package and live tests were not rerun.
| /** Conversational opt-out patterns that instruct the assistant not to persist the turn. */ | ||
| const OPT_OUT_PATTERNS = [ | ||
| new RegExp( | ||
| `${IMPERATIVE_NEGATION_PREFIX}(?:do\\s+not|don['\\u2019]?t|never)\\s+(?:ever\\s+)?(?:(?:remember|memorize|save|store|record|capture|keep)(?:\\s+or\\s+(?:remember|memorize|save|store|record|capture|keep))?)(?:\\s+(?:any(?:\\s+of)?(?:\\s+(?:this|that|these|those|it))?|anything(?:\\s+(?:here|from\\s+this))?|this|that|these|those|it|what\\s+i|my\\b|our\\b|(?:in|to)\\s+memory|the\\s+(?:conversation|chat|message|turn|discussion|note|notes|details))\\b|(?=\\s*(?:$|[.!?:;,)\\]])))`, |
There was a problem hiding this comment.
[P2] Preserve explicit remember opt-outs with other objects
Combining remember/memorize with the save/store object whitelist makes ordinary opt-outs such as “Do not remember the following: I prefer working from Tokyo next month.” and “Please don't remember where I live: 123 Example Street.” return hasCaptureOptOut=false and shouldCapture=true. Both were rejected at the previous head. The hook therefore leaves optOutActive false and forwards both the user content and the trailing assistant acknowledgement to analyze(); the offline hook probe produced one request containing both messages. Keep the broader object handling for explicit remember/memorize imperatives, or cover these forms without weakening the developer-prose exclusions, and add a hook regression.
ducnmm
left a comment
There was a problem hiding this comment.
The previously reported remember opt-outs are fixed and the four focused source-level tests pass. Offline predicate and hook probes found a new regression: ordinary coordinated first-person statements are now classified as opt-outs and the hook drops both the statement and the assistant reply. Full package and live tests were not rerun.
| /** Conversational opt-out patterns that instruct the assistant not to persist the turn. */ | ||
| const OPT_OUT_PATTERNS = [ | ||
| new RegExp( | ||
| `${IMPERATIVE_NEGATION_PREFIX}(?:do\\s+not|don['\\u2019]?t|never)\\s+(?:ever\\s+)?(?:remember|memorize)(?:\\s+or\\s+(?:save|store|record|capture|keep))?\\b`, |
There was a problem hiding this comment.
[P2] Preserve coordinated first-person developer statements
The shared prefix accepts and/but, so broadening remember to any object now treats ordinary statements such as “I prefer Rust but don't remember when we adopted it.” and “We use Postgres and don't remember why we picked that version.” as instructions not to save the turn. Both return hasCaptureOptOut=true and shouldCapture=false here, while the previous head captured them. The hook also suppresses the trailing assistant response; an offline hook probe made zero analyze calls for the Rust preference plus its acknowledgement. Distinguish coordinated first-person prose from an imperative before marking the whole turn opted out, and add a regression alongside the standalone “I don't remember” case.
There was a problem hiding this comment.
Fixed in 84bffe3. Broadened remember/memorize opt-out matching now requires a clause-initial or polite imperative boundary (CLAUSE_IMPERATIVE_PREFIX), while coordinated conjunctions (and, but, ,) only trigger when paired with an explicit persistence verb or deictic turn target (this, that, it, anything, the following, the conversation). Added unit and hook-level regressions for coordinated first-person statements like "I prefer Rust but don't remember when we adopted it." and "We use Postgres and don't remember why we picked that version.".
ducnmm
left a comment
There was a problem hiding this comment.
The coordinated first-person examples now remain capturable, and the four focused source-level tests pass, including the earlier opt-out and assistant-echo regressions. An offline predicate/hook probe found a new boundary regression: an explicit privacy-directed remember opt-out after a comma now forwards the user content and assistant acknowledgement to analyze(). Full package and live tests were not rerun.
| ]; | ||
|
|
||
| const CLAUSE_IMPERATIVE_PREFIX = | ||
| "(?:^|[.!?;:(\\[\\n]\\s*|\\b(?:please|just)\\s+|\\byou\\s+(?:must\\s+|should\\s+|can\\s+)?)"; |
There was a problem hiding this comment.
[P2] Preserve explicit remember opt-outs after a comma
Removing the comma boundary from broad remember matching means “For privacy, don't remember my home address: 123 Example Street.” and “For privacy, do not remember where I live: 123 Example Street.” now return hasCaptureOptOut=false and shouldCapture=true. The fallback pattern also excludes my and where I, so neither explicit instruction is recognized, although both were excluded at the previous head. A hook probe for the first example now makes one analyze() call containing both the address and the assistant's acknowledgement, versus zero previously. Preserve unambiguous privacy-directed imperatives after a comma while retaining the coordinated-prose exclusions, and add a hook regression for this form.
|
Addressed review feedback in 93ddbf0: preserved the comma boundary in clause imperatives and added my, our, and where/what/who/how I to the remember fallback pattern while keeping coordinated-prose exclusions intact. Added unit tests for privacy-directed imperatives after commas and a hook-level regression confirming assistant echoes are suppressed. |
ducnmm
left a comment
There was a problem hiding this comment.
The privacy-directed comma examples and the four focused opt-out tests pass. An offline comparison with the previous head found the new conjunction regression below; the hook forwards both the opted-out user content and its assistant acknowledgement to analyze(). No live requests were performed.
| const CLAUSE_IMPERATIVE_PREFIX = | ||
| "(?:^|[.!?;:,(\\[\\n]\\s*|\\b(?:please|just)\\s+|\\byou\\s+(?:must\\s+|should\\s+|can\\s+)?)"; | ||
|
|
||
| const IMPERATIVE_NEGATION_PREFIX = CLAUSE_IMPERATIVE_PREFIX; |
There was a problem hiding this comment.
[P2] Preserve explicit opt-outs after conjunctions
Aliasing this prefix removes the previous and/but boundaries from the narrow fallback patterns. Consequently, My temporary medical appointment is at 3 PM and do not remember this. and My home address is 123 Example Street but do not save this in memory. now return hasCaptureOptOut=false and shouldCapture=true; both were excluded at the previous head. A focused hook probe makes one analyze() call containing the user message and the trailing assistant acknowledgement. Retain conjunction handling for explicit persistence verbs/deictic opt-out targets while preserving the coordinated first-person developer-prose exclusions, and add hook-level regressions for these forms.
93ddbf0 to
0759d2e
Compare
|
Fixed in |
1dd3efd to
e9e3d39
Compare
|
Rebased onto dev cleanly. All 50 OpenClaw tests pass. Ready for review. |
ducnmm
left a comment
There was a problem hiding this comment.
The prior opt-out grammar regressions are fixed and the focused opt-out suite passes (4 tests). The separate secret-echo path below still forwards a detected credential to extraction. Verification was offline; no live writes were performed.
| const msg = messages[i]; | ||
| if (!msg || typeof msg !== "object") continue; | ||
| if (msg.role === "user") { | ||
| optOutActive = hasCaptureOptOut(rawMessageText(msg)); |
There was a problem hiding this comment.
[P2] Suppress assistant echoes of secret-bearing user turns
The new turn-level prefilter suppresses assistant replies only when the user has an opt-out directive. A user message detected by looksLikeSecret is instead dropped later by the individual-message filter, while a paraphrased assistant echo remains capturable. On this head, Staging test account uses password: hunter2 for integration runs is rejected, but the reply For integration testing, your staging database password is hunter2. passes shouldCapture; an offline call through the actual registerCaptureHook sends that reply, including hunter2, to analyze(). This defeats the added inline-secret exclusion whenever the assistant repeats a credential without its assignment syntax. Use the same turn suppression for a detected-secret user message until the next normal user turn, and cover the assistant-echo path in the hook test.
|
Addressed in commit f96d82b:
|
ducnmm
left a comment
There was a problem hiding this comment.
The previously reported password-bearing user turn now suppresses its assistant echo, and the next normal turn remains capturable. Session-cookie headers are still missed by secret detection, so their turns and assistant echoes reach analyze. The TypeScript build and all 18 focused capture, security, and cache tests pass. The additional actual-hook probe used a fake analyze client and synthetic cookie values, with no live requests.
| /\b(?:glpat-[A-Za-z0-9_-]{16,}|npm_[A-Za-z0-9]{36}|[sprk]k_(?:live|test)_[A-Za-z0-9]{16,}|hf_[A-Za-z0-9]{30,})\b/, | ||
| /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/, | ||
| /[A-Za-z][A-Za-z0-9+.-]{0,30}:\/\/[^\s/@:?=&]+:[^\s/@?=&]+@/, | ||
| /\b(?:(?:proxy-)?authorization\s*[:=]\s*(?:bearer|basic|token|digest)\s+\S+|bearer\s+[A-Za-z0-9._~+/=-]{20,})/i, |
There was a problem hiding this comment.
[P1] Detect session-cookie headers as secret-bearing turns
The header rule covers Authorization/Bearer but no Cookie or Set-Cookie form. For staging, send Cookie: sid=synthetic-session-cookie-value when reproducing dashboard login. therefore returns looksLikeSecret=false; an actual registerCaptureHook probe forwards that header and the assistant's paraphrased session value to analyze(). Set-Cookie: sid=synthetic-set-cookie-value; HttpOnly; Secure has the same result. Because the new turn suppression relies on this predicate, it cannot protect either the original cookie or its echo. Recognize Cookie/Set-Cookie headers before turn filtering and cover both outgoing hook payloads, while retaining ordinary cookie-related prose without credential values.
…ets in shouldCapture
…ns, bump to 0.0.9
f96d82b to
24aa040
Compare
ducnmm
left a comment
There was a problem hiding this comment.
Offline inspection and synthetic probes found an automatic-save privacy issue. No live upload was performed.
| /(?:\b|(?<=[a-z0-9_-]))(?:session[_-]?cookies?|session[_-]?ids?|connect\.sid)\s*[:=]\s*(?:["'`](?!\s*["'`])[^"'`\n]{4,}["'`]|(?!(?:string|number|boolean|null|undefined|none|true|false)\b)[^\s'",;}{)\]]{6,})/i, | ||
| /(?:\b|(?<=[a-z0-9_-]))(?:passwords?|passwd|pwd|db[_-]?pass(?:word)?s?|passphrases?|api[_-\s]?keys?|apikeys?|secret[_-\s]?keys?|access[_-\s]?keys?|encryption[_-\s]?keys?|client[_-\s]?secrets?|secrets?|access[_-\s]?tokens?|refresh[_-\s]?tokens?|auth[_-\s]?tokens?|bearer[_-\s]?tokens?|session[_-\s]?tokens?|api[_-\s]?tokens?|private[_-\s]?keys?|priv[_-\s]?keys?|delegate[_-\s]?(?:private[_-\s]*)?keys?)["'`]?\s*[:=]\s*(?:["'`](?!\s*["'`])[^"'`\n]{4,}["'`]|(?!(?:string|number|boolean|null|undefined|none|true|false|required|optional)\b)[^\s'",;}{)\]]{6,})/i, | ||
| /\btoken["'`]?\s*[:=]\s*(?:["'`](?!\s*["'`])[^"'`\n]{8,}["'`]|(?!(?:string|number|boolean|null|undefined|none|true|false|required|optional)\b)[^\s'",;}{)\]]{12,})/i, | ||
| /(?:private[\s_-]*key|secret[\s_-]*key|delegate[\s_-]*(?:private[\s_-]*)?key|signing[\s_-]*key|api[\s_-]*key|seed)\b[^.\n]{0,32}\b(?:0x)?[0-9a-fA-F]{64}\b/i, |
There was a problem hiding this comment.
[P1] Detect labelled private keys across Markdown line breaks
The labelled-hex rules exclude newlines. Both # Delegate private key\n + 64 synthetic a characters and My delegate private key is\n + that value return looksLikeSecret=false and shouldCapture=true on this head. They therefore bypass the new secret-bearing-turn suppression and remain eligible for analyze(). This is a normal Markdown key layout. Match labels across line breaks before capture, and add a regression for the complete user/assistant turn; ordinary unlabelled public identifiers can stay capturable.
ducnmm
left a comment
There was a problem hiding this comment.
The multiline labelled-secret fix passes the TypeScript build and 18 focused capture/security/cache tests, including the assistant-echo regression. One nonblocking changelog metadata cleanup remains below. No live upload was performed; this is not an approval or a claim that CI is green.
| - When was the MemWal OpenClaw plugin first released? | ||
| answer: >- | ||
| Version 0.0.9 prevents sub-agent namespaces and legacy aliases from colliding with the main agent's defaultNamespace, rejects sub-agent memory_search and memory_store requests for the main namespace, and updates the MemWal SDK dependency to 0.1.13. Version 0.0.8 bounds and expires idle capture-session state, watermarks auto-capture per session and preserves message timestamps for occurredAt, aborts timed-out analyze requests without blind retries, distinguishes accepted async remember jobs from completed storage in memory_store, updates the MemWal SDK dependency to 0.1.10, and detects letter-spaced prompt injection keywords. | ||
| answer: >- |
There was a problem hiding this comment.
[P3] Remove the nested answer marker from the folded summary
This answer: >- is already inside the top-level folded answer value, so YAML treats it as literal summary text. Parsing the frontmatter produces a summary beginning with answer: >- rather than the version description. Remove this redundant marker and keep the summary indented directly under the original field.
|
@ducnmm Removed the redundant nested |
Summary
looksLikeOptOutandlooksLikeSecretchecks inpackages/openclaw-memory-memwal/src/capture.tsahead ofTRIGGER_PATTERNSso explicit conversational opt-out directives (such asdo not remember,don't save,off the record,[no-save]) and inline secret patterns (suiprivkey1, vendor API keys, PEM blocks, Bearer headers, and labelled 64-hex keys) are rejected locally beforeregisterCaptureHookcallsclient.analyze().packages/openclaw-memory-memwal/test/capture-optout.test.mjs.Verification
@mysten-incubation/dev-walletbefore and after the fix:shouldCapturenow returnsfalsefor both opt-out and secret turns andregisterCaptureHooksends0requests to/api/analyze.pnpm --dir packages/openclaw-memory-memwal test(48 passed).cc @ducnmm